import java.util.Stack;


public class RuleLambdaApply {

	/*
	 * lambda:3:x:env#
	 */
	
	Stack<ControlElement> control ;
	Stack<String> stack ;
	Environment environment;
	ControlStructure ct;
		
	public RuleLambdaApply(Stack<ControlElement> control, Stack<String> stack, Environment environment, ControlStructure ct){
		this.control = control;
		this.stack = stack;
		this.environment = environment;
		this.ct = ct;
	}
	
	public static void main(String[] args) {
		Stack<ControlElement> control = new Stack<ControlElement>();
		Stack<String> stack = new Stack<String>();
		Environment environment = new Environment();
		RuleLambdaApply ruleLambda = new RuleLambdaApply(control, stack, environment, null);

		// test1 
		control.push(new ControlElement("<lambda:2:y>"));
		System.out.println(ruleLambda.checkRule());
		//ruleOP.devide();
		//ruleOP.power();
		ruleLambda.applyRule();
		System.out.println("Control:"+ruleLambda.control + " Stack:" +ruleLambda.stack);
	}
	
	/*
	 * lambda:3:x:#
	 */
	public void applyRule() {
		// poping 'gamma'
		control.pop();
		String lambdaClosure = stack.pop();
		boolean multiVar = checkMultiVar(lambdaClosure);
		if (multiVar == true) {
			applyMultiVar(lambdaClosure);
		}else{
			applySingleVar(lambdaClosure);
		}
	}

	/*
	 * Things that can be on stack are : primitive data types, tuples, functions
	 * Stack can have lambdacloure than (lambdacloure|primitive|tau)
	 * 
	 * 1. create a new environment
	 * 2. assign the defined var in lambdaClousure to the operant
	 * 3. spawn the new environment as a child of the lambda create environement
	 * 3. load the new enironment on both stack and control
	 */
	private void applySingleVar(String lambdaClosure) {
		int lambdaCreateEnv = getCreateEnv(lambdaClosure);
		
		// new env is a child of the lambda create environment
		int newEnv = environment.addEnv(lambdaCreateEnv);
		String operant = stack.pop();
		environment.currentEnv = newEnv;
		String object = "<ID:"+getDef(lambdaClosure)+">";
		environment.add(newEnv, object, operant);
		control.push(new ControlElement("e:"+newEnv));
		deltaDump(getDelta(lambdaClosure));
		stack.push("e:"+newEnv);
		environment.envLoaded.push(newEnv);
	}

	private void applyMultiVar(String lambdaClosure) {
		int lambdaCreateEnv = getCreateEnv_Multi(lambdaClosure);
		int newEnv = environment.addEnv(lambdaCreateEnv);
		String operant = stack.pop();
		environment.currentEnv = newEnv;
		
		if (!operant.substring(0, 7).equals("$$tau::")){
			System.out.println("expected a tuple on the other side of assignment");
		}
		
		String objects[] = extractMultiObjects(lambdaClosure);
		String values[] = extractMultiOperants(operant);
		
		if ((objects.length-4) != (values.length-1)){
			System.out.println("expected order of tuple to be equal to the definition");
			System.exit(0);
		}
		
		for(int i = 3 ; i < (objects.length-1) ; i++){
			environment.add(newEnv, "<ID:"+objects[i]+">", values[i-2]);
		}
		control.push(new ControlElement("e:"+newEnv));
		deltaDump(getDelta(lambdaClosure));
		stack.push("e:"+newEnv);
		environment.envLoaded.push(newEnv);	
	}
		
	/*
	 * environment in which lambda was created, which is the last parameter of lambda closure
	 */
	private int getCreateEnv_Multi(String lambdaClosure) {
		String temp[] = lambdaClosure.split(":");
		return Integer.parseInt(temp[temp.length-1]);
	}

	/*
	 * tau format needed
	 * order = #operants - 2 
	 */
	private String[] extractMultiOperants(String operant) {
		StackTupleHandler tuple = new StackTupleHandler(operant);
		return tuple.pieces;
	}

	/*
	 * multi lambda format needed
	 * order = #operants - 3
	 */
	private String[] extractMultiObjects(String lambdaClosure) {
		return lambdaClosure.split(":");
	}

	private boolean checkMultiVar(String lambdaClosure) {
		if (lambdaClosure.contains("multivar")){
			return true;
		}else{
			return false;
		}
	}

	private void deltaDump(int deltaNo) {
		for (int i = 0; i < ct.delta[deltaNo].size(); i++) {
			control.push(ct.delta[deltaNo].get(i));
		}
	}

	private int getCreateEnv(String lambdaClosure) {
		String temp = lambdaClosure.split(":")[3];
		return Integer.parseInt(temp);
	}
	
	/*
	 * variable the lambda closure defines
	 */
	private String getDef(String lambdaClosure) {
		String temp = lambdaClosure.split(":")[2];
		return temp;
	}

	private int getDelta(String lambdaClosure) {
		String temp = lambdaClosure.split(":")[1];
		return Integer.parseInt(temp);
	}
	
	public boolean checkRule() {
		if (control.peek().content.contains("gamma") && 
				stack.peek().length() > 7 && 
				stack.peek().substring(0, 7).contains("lambda:"))
			return true;
		else
			return false;
	}
}
